/*
 * BlackLightning.PluginLib.Modules.CommandPacket
 * Andy Tidball
 * 
 * Project: Black Lightning Plugin Library
 * Copyright: GNU General Public License
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using BLLib.Net;

namespace BlackLightning.PluginLib.Modules {
	/// <summary>
	/// A packet that represents a command sent by the user from the front end to the game server or Black Lightning.
	/// </summary>
	[Serializable]
	public class CommandPacket : CommonFEPacket {
		// member data
		private string   _Command; // the command being issued
		private string[] _Args;    // arguments passed to the command

		/// <summary>
		/// Creates a new CommandPacket.
		/// </summary>
		/// <param name="Source">The program that the packet came from.</param>
		/// <param name="Destination">The program that the packet will be delivered to.</param>
		/// <param name="Command">The command to contain in the packet.</param>
		/// <param name="Arguments">The arguments given to the command contained in the packet.</param>
		public CommandPacket(Program Source, Program Destination, string Command, string[] Arguments) 
			: base(Source, Destination, null) {
			_Command = Command;
			_Args    = Arguments;
		}

		/// <summary>
		/// Creates a new CommandPacket.
		/// </summary>
		/// <param name="Source">The program that the packet came from.</param>
		/// <param name="Destination">The program that the packet will be delivered to.</param>
		/// <param name="Data">The data string contained in the packet.</param>
		/// <param name="Context">Contextual information to use to parse information from the given data.</param>
		internal CommandPacket(Program Source, Program Destination, string Data, BLPacketContext Context)
			: base(Source, Destination, Data, Context) {
			// first, trim any whitespace off the data
			Data = Data.Trim();

			// if we're using StormFront's protocol, then commands start with "<c>", trim it off
			if (Context.Protocol == Protocol.StormFront) {
				if (!Data.StartsWith("<c>")) {
					throw new ArgumentException("All StormFront commands start with '<c>' but the given data does not.");
				}
				Data = Data.Substring(3);
			}

			// check if this is a speech command
			Regex SpeechRegex = new Regex("^('|\"|say\\s+)\\s*(:{1,2}\\w+)?\\s*(:{1,2}\\w+)?\\s*(.+)$");
			Match M = SpeechRegex.Match(Data.Replace(Environment.NewLine, "\n"));
			if (M.Success) {
				// break up the data
				string Verb = M.Groups[1].Value;
				string One  = M.Groups[2].Success ? M.Groups[2].Value : "";
				string Two  = M.Groups[3].Success ? M.Groups[3].Value : "";
				string Text = M.Groups[4].Value;

				// recreate the data in a standard fashion
				Data = Verb + " ";
				if (One != string.Empty) {
					Data += One + " ";
				}
				if (Two != string.Empty) {
					Data += Two + " ";
				}
				Data += "\"" + Text + "\"";
			}

			// split up the data into command and arguments
			string[] Split = Data.Split(' ');
			_Command = Split[0];

			// run through the arguments
			List<string> Args = new List<string>();
			for (int i=1; i<Split.Length; ++i) {
				if (Split[i].StartsWith("\"")) {
					// if this argument starts with a quote, it doesn't end until the next argument that ends with a quote
					string Arg = "";

					// keep building up the argument until we end with a quote or we're out of text
					while (i<Split.Length && !Arg.EndsWith("\"")) {
						if (Arg != "") {
							Arg += " ";
						}
						Arg += Split[i];
						++i;
					}
					--i; // negate the ++i that will happen at the end of the outer loop, since we already incremented it

					// remove the beginning quote
					Arg = Arg.Substring(1);

					// remove the trailing quote
					if (Arg.EndsWith("\"")) {
						Arg = Arg.Substring(0, Arg.Length-1);
					}

					// add the argument to the list
					Args.Add(Arg);
				} else {
					// add this argument to the list
					Args.Add(Split[i]);
				}
			}

			// store the arguments
			_Args = Args.ToArray();
		}

		/// <summary>
		/// Copy constructor.
		/// </summary>
		/// <param name="P">The CommandPacket to create a copy of.</param>
		/// <param name="DeepCopyOrNextState">True to make a deep copy of the given packet, false to instead use the given packet as the new packet's previous state.</param>
		internal CommandPacket(CommandPacket P, bool DeepCopyOrNextState)
			: base(P, DeepCopyOrNextState) {
			_Command = P._Command;
			_Args    = new string[P._Args.Length];
			P._Args.CopyTo(_Args, 0);
		}

		/// <summary>
		/// Gets or sets the command contained in the packet.
		/// </summary>
		public string Command {
			get {
				return _Command;
			}
			set {
				_Command = value;
			}
		}

		/// <summary>
		/// Gets or sets the arguments sent to the command.
		/// </summary>
		public string[] Arguments {
			get {
				return _Args;
			}
			set {
				_Args = value;
			}
		}

		/// <summary>
		/// Gets whether or not this command represents a speech command (the command is ', ", or "say")
		/// </summary>
		public bool IsSpeechCommand {
			get {
				return (Command == "'" || Command == "\"" || Command == "say");
			}
		}

		/// <summary>
		/// Gets the tone of a speech command, without the : in front.  Returns null if this isn't a speech command or no tone was specified.
		/// </summary>
		public string SpeechTone {
			get {
				// if this isn't a speech command, don't bother
				if (!IsSpeechCommand) {
					return null;
				}

				// check the arguments for one beginning with :
				foreach (string Arg in Arguments) {
					if (Arg[0] == ':' && Arg[1] != ':') {
						return Arg.Substring(1);
					}
				}

				// didn't find it.
				return null;
			}
		}

		/// <summary>
		/// Gets the target of a speech command, without the :: in front.  Returns null if this isn't a speech command or no target was specified.
		/// </summary>
		public string SpeechTarget {
			get {
				// if this isn't a speech command, don't bother
				if (!IsSpeechCommand) {
					return null;
				}

				// check the arguments for one beginning with ::
				foreach (string Arg in Arguments) {
					if (Arg[0] == ':' && Arg[1] == ':') {
						return Arg.Substring(2);
					}
				}

				// didn't find it.
				return null;
			}
		}

		/// <summary>
		/// Retrieves the packet's data.
		/// </summary>
		/// <param name="Context">Contextual information to use to create output from the packet's data.</param>
		/// <returns>The packet's data string.</returns>
		public override string CreateOutput(PacketContext Context) {
			// down-cast the context to something more useful
			BLPacketContext BLContext = Context as BLPacketContext;
			if (BLContext == null) {
				throw new ArgumentException("Given Context must be of the type BLPacketContext.");
			}

			StringBuilder Output = new StringBuilder();

			// the StormFront protocol prepends all commands with a <c> tag
			if (BLContext.Protocol == Protocol.StormFront) {
				Output.Append("<c>");
			}

			// append the command itself
			Output.Append(_Command);

			// run through and append each argument
			foreach (string Arg in _Args) {
				Output.Append(" ");
				if (!IsSpeechCommand && Arg.Contains(" ")) {
					Output.Append("\"" + Arg + "\"");
				} else {
					Output.Append(Arg);
				}
			}

			// return the result
			return Output.ToString();
		}

		/// <summary>
		/// Creates a deep copy of the packet.
		/// </summary>
		/// <returns>A deep copy of the packet.</returns>
		public override object Clone() {
			return new CommandPacket(this, true);
		}

		/// <summary>
		/// Creates a new CommandPacket that has this CommandPacket as its PreviousState.
		/// </summary>
		/// <returns>A new CommandPacket that has this one as its PreviousState.</returns>
		internal override BLPacket GetNextState() {
			return new CommandPacket(this, false);
		}
	}

	/// <summary>
	/// A filter for CommandPackets.
	/// </summary>
	[Serializable]
	public class CommandPacketFilter : CommonFEPacketFilter {
		// member data
		private string   _CommandString = null; // the string to match against the packet's command
		private Regex    _CommandRegex  = null; // the regular expression to match against the packet's command
		private string[] _ArgStrings    = null; // an array of strings to match against the packet's arguments
		private Regex[]  _ArgRegexes    = null; // an array of regular expressions to match against the packet's arguments
		private bool?    _IsSpeech      = null; // whether or not the filter will only match speech commands

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="IsSpeechCommand">The boolean to match against the packet's IsSpeechCommand property.</param>
		public CommandPacketFilter(bool IsSpeechCommand) {
			_IsSpeech = IsSpeechCommand;
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Command">The string to match against the packet's command.</param>
		public CommandPacketFilter(string Command) {
			_CommandString = Command;
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Command">The regular expression to match against the packet's command.</param>
		public CommandPacketFilter(Regex Command) {
			_CommandRegex = Command;
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Command">The string to match against the packet's command.</param>
		/// <param name="Arguments">An array of strings to match against the packet's arguments.</param>
		public CommandPacketFilter(string Command, string[] Arguments) {
			_CommandString = Command;
			_ArgStrings    = Arguments;
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Command">The regular expression to match against the packet's command.</param>
		/// <param name="Arguments">An array of regular expressions to match against the packet's commands.</param>
		public CommandPacketFilter(Regex Command, Regex[] Arguments) {
			_CommandRegex = Command;
			_ArgRegexes   = Arguments;
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Source">The source program to match against the packet's source.</param>
		/// <param name="Destination">The destination program to match against the packet's destination.</param>
		public CommandPacketFilter(Program Source, Program Destination)
			: base(Source, Destination) {
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Source">The source program to match against the packet's source.</param>
		/// <param name="Destination">The destination program to match against the packet's destination.</param>
		/// <param name="IsSpeechCommand">The boolean to match against the packet's IsSpeechCommand property.</param>
		public CommandPacketFilter(Program Source, Program Destination, bool IsSpeechCommand)
			: base(Source, Destination) {
			_IsSpeech = IsSpeechCommand;
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Source">The source program to match against the packet's source.</param>
		/// <param name="Destination">The destination program to match against the packet's destination.</param>
		/// <param name="Command">The string to match against the packet's command.</param>
		public CommandPacketFilter(Program Source, Program Destination, string Command)
			: base(Source, Destination) {
			_CommandString = Command;
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Source">The source program to match against the packet's source.</param>
		/// <param name="Destination">The destination program to match against the packet's destination.</param>
		/// <param name="Command">The regular expression to match against the packet's command.</param>
		public CommandPacketFilter(Program Source, Program Destination, Regex Command)
			: base(Source, Destination) {
			_CommandRegex = Command;
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Source">The source program to match against the packet's source.</param>
		/// <param name="Destination">The destination program to match against the packet's destination.</param>
		/// <param name="Command">The string to match against the packet's command.</param>
		/// <param name="Arguments">An array of strings to match against the packet's arguments.</param>
		public CommandPacketFilter(Program Source, Program Destination, string Command, string[] Arguments)
			: base(Source, Destination) {
			_CommandString = Command;
			_ArgStrings    = Arguments;
		}

		/// <summary>
		/// Creates a new CommandPacketFilter.
		/// </summary>
		/// <param name="Source">The source program to match against the packet's source.</param>
		/// <param name="Destination">The destination program to match against the packet's destination.</param>
		/// <param name="Command">The regular expression to match against the packet's command.</param>
		/// <param name="Arguments">An array of regular expressions to match against the packet's commands.</param>
		public CommandPacketFilter(Program Source, Program Destination, Regex Command, Regex[] Arguments)
			: base(Source, Destination) {
			_CommandRegex = Command;
			_ArgRegexes   = Arguments;
		}

		/// <summary>
		/// Gets or sets the string to match against the packet's command.
		/// </summary>
		public string CommandString {
			get {
				return _CommandString;
			}
			set {
				_CommandString = value;
			}
		}

		/// <summary>
		/// Gets or sets the regular expression to match against the packet's command.
		/// </summary>
		public Regex CommandRegex {
			get {
				return _CommandRegex;
			}
			set {
				_CommandRegex = value;
			}
		}

		/// <summary>
		/// Gets or sets the strings to match against the packet's arguments.
		/// </summary>
		public string[] ArgumentStrings {
			get {
				return _ArgStrings;
			}
			set {
				_ArgStrings = value;
			}
		}

		/// <summary>
		/// Gets or sets the regular expressions to match against the packet's arguments.
		/// </summary>
		public Regex[] ArgumentRegexes {
			get {
				return _ArgRegexes;
			}
			set {
				_ArgRegexes = value;
			}
		}

		/// <summary>
		/// Gets or sets the boolean to match against the packet's IsSpeechCommand property.
		/// </summary>
		public bool? IsSpeechCommand {
			get {
				return _IsSpeech;
			}
			set {
				_IsSpeech = value;
			}
		}

		/// <summary>
		/// Matches the packet's dynamic data against the filter.
		/// </summary>
		/// <param name="Packet">The packet to match against the filter.</param>
		/// <returns>True if the packet's dynamic data matches the current setup of the filter, false if it does not.</returns>
		public override bool DynamicMatch(BLPacket Packet) {
			// make sure this is a CommandPacket
			CommandPacket P = Packet as CommandPacket;
			if (P == null) {
				return false;
			}

			// match the is speech property
			if (_IsSpeech != null && _IsSpeech != P.IsSpeechCommand) {
				return false;
			}

			// match the command
			if (_CommandString != null && _CommandString != P.Command) {
				return false;
			}
			if (_CommandRegex != null && !_CommandRegex.IsMatch(P.Command.Replace(Environment.NewLine, "\n"))) {
				return false;
			}

			// match the arguments
			if (_ArgStrings != null) {
				if (_ArgStrings.Length > P.Arguments.Length) {
					return false;
				}
				for (int i=0; i<_ArgStrings.Length; ++i) {
					if (_ArgStrings[i] != P.Arguments[i]) {
						return false;
					}
				}
			}
			if (_ArgRegexes != null) {
				if (_ArgRegexes.Length > P.Arguments.Length) {
					return false;
				}
				for (int i=0; i<_ArgRegexes.Length; ++i) {
					if (!_ArgRegexes[i].IsMatch(P.Arguments[i].Replace(Environment.NewLine, "\n"))) {
						return false;
					}
				}
			}

			// everything passed, match the base packet data
			return base.DynamicMatch(P);
		}
	}
}
